Android 截图,兼容android 5.0和大图片

Android 中选择一张图片然后截取部分作为头像是一个非常常见的需求。当然很多个性化的应用中都会有自己专门定制的选择图片和裁剪图片。但本文现在主要讨论的是一些系统的东西,包括打开系统图片的选择、调用系统的截图功能。

以前在调用系统的截图的时候我都是这样使用的。

1
2
3
4
5
6
7
8
9
Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setType("image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 120);
intent.putExtra("outputY", 120);
intent.putExtra("return-data", true);
startActivityForResult(intent, CROP_IMG);

这样写好像真的很简单也很好懂,Intent.ACTION_PICK 打开选择图片的界面,然后通过下面的intent 的一些设置就可以截取一张图片了。这样写在我的很多的机器上确实也没有什么问题,但是直到碰到了5.0 的手机。在5.0 的手机中,首先出现的一个问题是,上面的代码没有出现图片的裁剪功能。流程变成了选择图片,点击图片以后就选择了整张的图片。选择一整张图片确实是没问题,但是需求是必须给用户一个截取图片的过程(其实选取一整张图片还是有一个很严重的问题的,就是图片太大的问题)。

经过分析,这个的问题是在5.0 的手机中,只执行了一个选择图片的过程。或许应该将图片的选择和图片的截取分开才能完成自己的需求。

第一步:如何打开图片的选择界面。

1.Intent.ACTION_PICK = "android.intent.action.PICK";选择一个数据返回它的URI。返回的格式:content://media/external/images/media/67。在所有的版本中都是返回这样的格式。

2.Intent.ACTION_GET_CONTENT = "android.intent.action.GET_CONTENT";选择一个数据。

API 19及以上,返回的Uri:content://com.android.providers.media.documents/document/image%3A57425

API 19以下,返回的Uri:content://media/external/images/media/31956

在API 19中又可以从下载内容(其实在API 19及以上从很多不同的地方拿到的Uri不相同)中打开一些内容,Uri是这样的类型。
content://com.android.providers.downloads.documents/document/4

3.Intent.ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";(API 19以后才有这个),可以打开云端的文件。只有在API 19以上才可以使用,得到的Uri跟API 19在Intent.ACTION_GET_CONTENT中得到的一样。

所以第一步可以有三种选择来打开一个选择图片的界面。根据我们的需求应该选择ACTION_PICK 来做,因为只有这个可以兼容所有的版本。(当然使用ACTION_GET_CONTENT 和ACTION_OPEN_DOCUMENT 也是可以做的,但是区分其实蛮大的)
使用ACTION_PICK,打开图片选择的过程。

1
2
3
Intent intent = new Intent("android.intent.action.PICK");
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
startActivityForResult(intent, PICK_IMG);

在选中一张图片以后就会在onActivityResult中得到一张图片的Uri。然后就需要打开这张图片并裁剪。

第二步.如何的截取图片。

系统的截图 com.android.camera.action.CROP

1
2
3
4
5
6
7
8
9
10
11
12
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*");
intent.putExtra("crop", "true"); //为true才会出现裁剪的框
intent.putExtra("aspectX", 1); //宽高的比例
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", width); // 裁剪得到的宽
intent.putExtra("outputY", height);// 裁剪得到的高
intent.putExtra("return-data", true); // 返回数据
intent.putExtra("scale", true);
intent.putExtra("scaleUpIfNeeded", true); // 去掉黑边
intent.putExtra("noFaceDetection", true); // 不需要人脸识别
startActivityForResult(intent, CUT_IMG);

这样就可以在onActivityResult 方法中做相关的处理。
Bitmap bitmap = data.getParcelableExtra("data");
其实只有基于AOSP 的Android 手机才会具有com.android.camera.action.CROP,所以需要做相应的异常的处理。所以需要加一个ActivityNotFoundException的异常的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
try
{
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", width);
intent.putExtra("outputY", height);
intent.putExtra("return-data", true);
intent.putExtra("scale", true);
intent.putExtra("scaleUpIfNeeded", true);
intent.putExtra("noFaceDetection", true);
startActivityForResult(intent, CUT_IMG);
}
catch (ActivityNotFoundException e)
{
// 如果手机不是基于AOSP的话就是不能这么做的。可以直接通过出过来的uri来得到整张的图片
Bitmap bitmap = null;
bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
}

这样在很多机器上测试的时候都没有问题,但是这样就结束了吗?还没有。当图片很大的时候,这样还是会出现问题,一整张图片可能直接会使内存溢出,或者说裁剪的一张图片会使内存溢出(特别是对手机像素很高的来说)。所有呢,最好的情况是对图片进行压缩。

1
2
3
4
5
6
7
8
9
10
11
BitmapFactory.Options opts = new Options();
opts.inJustDecodeBounds = true; // 为true的话就不会decode的时候生成Bitmap。
BitmapFactory.decodeStream(is, null, opts);
// 但是可以通过opts拿到图片的宽和高
int bmpWidth = opts.outWidth;
int bmpHeight = opts.outHeight;
// 选择一个比较大的比例,这样缩放以后就不会放不下
int scale = Math.max(bmpWidth / 300, bmpHeight / 300);
opts.inSampleSize = scale; // 设置缩放的比例
// 这样得到的就是一个压缩以后的图片,一般不会内存溢出(注意这个地方是is1)
Bitmap bitmap = BitmapFactory.decodeStream(is1, null, opts);

所以呢可以专门写一个用来压缩的函数。(我这里是指定大小为300,300)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
* 通过Uri得到压缩以后的图片
*/
private Bitmap getBitmapFromBigImagByUri(Uri uri)
{
Bitmap result = null;
InputStream is1 = null;
InputStream is2 = null;
try
{
// 如果图片太大,这个地方依旧会出现问题
// Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
// 使用两个inputstream的原因
// http://stackoverflow.com/questions/12841482/resizing-bitmap-from-inputstream
is1 = getContentResolver().openInputStream(uri);
is2 = getContentResolver().openInputStream(uri);
BitmapFactory.Options opts1 = new Options();
opts1.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is1, null, opts1);
int bmpWidth = opts1.outWidth;
int bmpHeight = opts1.outHeight;
int scale = Math.max(bmpWidth / 300, bmpHeight / 300);
BitmapFactory.Options opts2 = new Options();
// 缩放的比例
opts2.inSampleSize = scale;
// 内存不足时可被回收
opts2.inPurgeable = true;
// 设置为false,表示不仅Bitmap的属性,也要加载bitmap
opts2.inJustDecodeBounds = false;
result = BitmapFactory.decodeStream(is2, null, opts2);
}
catch (Exception ex)
{
}
finally
{
if (is1 != null)
{
try
{
is1.close();
}
catch (IOException e1)
{
}
}
if (is2 != null)
{
try
{
is2.close();
}
catch (IOException e2)
{
}
}
}
return result;
}